package protocol;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.util.Random;
import java.util.TreeMap;

public class RoomConnection {

	// ConectionDiscover is the manager
	private ConnectionDiscover manager;

	// Users in the room
	private TreeMap<String, User> usersInRoom;

	// Control variables
	private boolean running, canSend, connectionConfirmed;

	// Listeners
	private OnNewMessageListener listener;
	private OnGameStartListener startingGameListener;
	private OnConnectionConfirmedListener confimationListener;

	// Announcing a Game
	private XmlBuilder announcingGameXml;
	private int privatePort = -1;
	private DatagramSocket sendingDatagram;
	private MulticastSocket privateGameSocketToReceive;
	//	private int roomSize;
	private String gameName, gamePassword;
	private boolean announcingGame, gameCreator;

	private final int MAX_MESSAGE_SIZE;

	/**
	 * 
	 */
	private static final int DEFAULT_TRUSTED_VALUE = 10;
	private static final int DEFAULT_MINIMUM_VALUE = 3;

	/**
	 * Message consecutive
	 * This value tracks the number of message sent
	 * The initial value is DEFAULT_TRUSTED_VALUE because this is the number of times it will receive the OnGameStart message
	 */
	private long consecutive = DEFAULT_TRUSTED_VALUE;

	/**
	 * Contiene el minimo de veces que debe ser enviado un mensaje debido a la calidad de la red
	 */
	private int fuzziness = DEFAULT_TRUSTED_VALUE + DEFAULT_MINIMUM_VALUE;
	private long fuzzyIndex, lastFuzzyIndexReceived;
	private long sendingTime;
	public int getFuzziness() {return fuzziness;}

	/*
	 * Constructor to announce a Game
	 */
	public RoomConnection(ConnectionDiscover manager, OnNewMessageListener listener, OnGameStartListener startingGameListener, String gameStyle, String gameName, String gamePassword) throws IOException {

		// Saving instances
		this.manager = manager;
		this.listener = listener;
		this.startingGameListener = startingGameListener;

		// Flags
		this.running = true;
		this.announcingGame = true;
		this.gameCreator = true;
		this.connectionConfirmed = true;

		// Setting the minimum datagram size
		if(listener.messageSize > P.MIN_BYTES_BUFFER_SIZE)
			MAX_MESSAGE_SIZE = listener.messageSize;
		else
			MAX_MESSAGE_SIZE = P.MIN_BYTES_BUFFER_SIZE;

		// Opening the room
		usersInRoom = new TreeMap<String, User>();

		// Saving the instances
		this.manager = manager;
		this.listener = listener;

		// Saving the parameters
		//		this.roomSize = roomSize;
		this.gameName = gameName;
		this.gamePassword = gamePassword;

		// Using a random port
		Random ale = new Random();
		while(privatePort == -1) {
			privatePort = ale.nextInt(65534) + 1;
			if(privatePort == ConnectionDiscover.MULTICAST_PORT)
				privatePort = -1;
		}

		// Initializing the socket to receive
		sendingDatagram = new DatagramSocket();
		privateGameSocketToReceive = new MulticastSocket(privatePort);
		privateGameSocketToReceive.joinGroup(manager.broadcastIP);

		// Creating the packet with the announcing information
		announcingGameXml = new XmlBuilder(P.ENCABEZADO);
		announcingGameXml.addChildren(new XmlBuilder(P.PROTOCOL_VERSION).setValue(P.VERSION_CODE));
		announcingGameXml.addChildren(new XmlBuilder(P.GAME_STYLE).setValue(gameStyle));
		announcingGameXml.addChildren(new XmlBuilder(P.GAME_NAME).setValue(gameName));
		announcingGameXml.addChildren(new XmlBuilder(P.PORT).setValue(""+privatePort));

		// Start announcing the game
		announcingThread();

		// Start the listening thread to check-in users to the room
		listeningIncomingConnections();
	}

	// ************************************** Creating a Game *************************************

	/**
	 * To stop sending messages
	 */
	public void stopGameAnnouncing() {
		synchronized (announcingGameXml) {
			announcingGameXml = null;
		}
	}

	/**
	 * This will announce a game to other users in the network in a broadcast message
	 */
	private void announcingThread() {

		final byte[] buf = announcingGameXml.toString().getBytes();

		new Thread(new Runnable() {

			@Override
			public void run() {

				// Aleatorio para el time out
				Random ale = new Random();

				while(announcingGame && announcingGameXml != null) {

					// Tiempo por fuera para no congestionar la red
					try{Thread.sleep(100 + ale.nextInt(100));}catch(InterruptedException ie){}

					try {
						sendingDatagram.send(new DatagramPacket(buf, buf.length, manager.broadcastIP, ConnectionDiscover.MULTICAST_PORT));
					} catch (IOException e) {
						Print.print("RoomConnection-announcingThread()-IOException");
					}
				}
			}
		}).start();
	}

	public void listeningIncomingConnections() {

		// Listening thread
		new Thread(new Runnable() {

			@Override
			public void run() {

				while(announcingGame && running) {
					try {
						byte[] buf = new byte[MAX_MESSAGE_SIZE];
						DatagramPacket paquete = new DatagramPacket(buf, buf.length);
						privateGameSocketToReceive.receive(paquete);
						String mensaje = new String(buf);

						//						Print.print(mensaje);

						// Checking if the initial structure is correct
						mensaje = XmlParser.readByTag(mensaje, P.ENCABEZADO, 0);
						if(mensaje != null) {

							// Checking the protocol version
							if(P.VERSION_CODE.equals(XmlParser.readByTag(mensaje, P.PROTOCOL_VERSION, 0))) {

								mensaje = XmlParser.readByTag(mensaje, P.CONNECTION_SYNC, 0);

								try {

									// Game Settings
									String theGameName = XmlParser.readByTag(mensaje, P.GAME_NAME, 0);
									String userName = XmlParser.readByTag(mensaje, P.USERNAME, 0);
									String password = XmlParser.readByTag(mensaje, P.PASSWORD, 0);

									// Checking if the package matches this room
									if(gameName.equals(theGameName)) {

										if(gamePassword.equals(password)) {

											// Checking in user
											InetAddress ip = paquete.getAddress();
											usersInRoom.put(ip.getHostAddress(), new User(ip.getHostAddress(), userName));
											startingGameListener.onUsersListUpdated(usersInRoom.values());

											// Sending confirmation message
											confirmUser(true, ip.getHostAddress());
										} else {
											// Sending denial message
											confirmUser(false, paquete.getAddress().getHostAddress());
										}

									}

								} catch(Exception e) {}
							}
						}

					} catch (IOException e) {
						Print.print("RoomConnection-listeningIncomingConnections()-IOException");
					}
				}

				// Announce that the game will start now
				//				if(running) {
				//
				//					XmlBuilder startGameTrigger = new XmlBuilder(P.ENCABEZADO);
				//					startGameTrigger.addChildren(new XmlBuilder(P.PROTOCOL_VERSION).setValue(P.VERSION_CODE));
				//					XmlBuilder trigger = new XmlBuilder(P.CONNECTION_SYNC);
				//					XmlBuilder localTime = new XmlBuilder(P.LOCALTIME);
				//					XmlBuilder countDown = new XmlBuilder(P.COUNT_DOWN);
				//					trigger.addChildren(localTime);
				//					trigger.addChildren(countDown);
				//					String usersToBeSent = manager.localIp;
				//					for (User user : usersInRoom.values()) {
				//						usersToBeSent += " "+user.ip.getHostAddress();
				//					}
				//					trigger.addChildren(new XmlBuilder(P.USERS_LIST).setValue(usersToBeSent));
				//
				//					startGameTrigger.addChildren(trigger);
				//
				//					for (int i = 4; i >= 0; i--) {
				//						startGameTrigger.recursiverResetOfToStrig();
				//						localTime.setValue(""+System.currentTimeMillis());
				//						countDown.setValue(""+i);
				//						//						Print.print("Will send this message: "+startGameTrigger.toString());
				//						byte[] buf = startGameTrigger.toString().getBytes();
				//						try {sendingDatagram.send(new DatagramPacket(buf, buf.length, manager.broadcastIP, privatePort));} catch (IOException e1) {}
				//						try {Thread.sleep(250);} catch (InterruptedException e) {}
				//					}
				//					canSend = true;
				//					listeningGameThread();
				//				}

			}
		}).start();
	}

	/**
	 * This method can be invoked only if you are announcing a game only once and if there is at least one user connected
	 * @return if the game has started. False is returned if it was invoked before.
	 */
	public boolean startGame() {

		if(running && announcingGame && usersInRoom.size() > 0) {

			announcingGame = false;
			privateGameSocketToReceive.close();
			try {
				privateGameSocketToReceive = new MulticastSocket(privatePort);
				privateGameSocketToReceive.joinGroup(manager.broadcastIP);
			} catch (IOException e2) {}

			new Thread(new Runnable() {

				@Override
				public void run() {

					// Building the starting message trigger
					XmlBuilder startGameTrigger = new XmlBuilder(P.ENCABEZADO);
					startGameTrigger.addChildren(new XmlBuilder(P.PROTOCOL_VERSION).setValue(P.VERSION_CODE));
					XmlBuilder trigger = new XmlBuilder(P.CONNECTION_SYNC);
					XmlBuilder localTime = new XmlBuilder(P.LOCALTIME);
					XmlBuilder countDown = new XmlBuilder(P.COUNT_DOWN);
					trigger.addChildren(localTime);
					trigger.addChildren(countDown);
					String usersIp = manager.localIp;
					String usersNames = gameName;
					for (User user : usersInRoom.values()) {
						usersIp += " " + user.ip;
						usersNames += " " + user.name;
					}
					trigger.addChildren(new XmlBuilder(P.USERS_LIST_IP).setValue(usersIp));
					trigger.addChildren(new XmlBuilder(P.USERS_LIST_NAMES).setValue(usersNames));
					startGameTrigger.addChildren(trigger);

					Print.print("finish building the trigger");

					// Sending the message the initial consecutive times
					for (int i = (int) DEFAULT_TRUSTED_VALUE; i > 0; i--) {
						Print.print("will send: "+i);
						startGameTrigger.recursiverResetOfToStrig();
						localTime.setValue(""+System.currentTimeMillis());
						countDown.setValue(""+i);
						//						Print.print("Will send this message: "+startGameTrigger.toString());
						byte[] buf = startGameTrigger.toString().getBytes();
						try {sendingDatagram.send(new DatagramPacket(buf, buf.length, manager.broadcastIP, privatePort));Print.print("Message sended: "+startGameTrigger);} catch (IOException e1) {Print.print("Not sended!!!!!!!!!!!!!!!!!!!!!!!!!!!");}
						try {Thread.sleep(150);} catch (InterruptedException e) {}
					}

					// Giving permisions to send messages
					canSend = true;
					listeningGameThread();
				}
			}).start();;

			return true;
		} else {
			return false;
		}
	}

	public void confirmUser(final boolean accept, final String hostIp) {

		new Thread(new Runnable() {

			@Override
			public void run() {

				XmlBuilder confirmation = new XmlBuilder(P.ENCABEZADO);
				XmlBuilder syncxml = new XmlBuilder(P.CONNECTION_SYNC);
				confirmation.addChildren(syncxml);
				syncxml.addChildren(new XmlBuilder(P.CONFIRMATION).setValue(accept ? P.TRUE : P.FALSE));
				syncxml.addChildren(new XmlBuilder(P.HOST_IP).setValue(hostIp));

				byte[] buf = confirmation.toString().getBytes();

				try {sendingDatagram.send(new DatagramPacket(buf, buf.length, manager.broadcastIP, privatePort));} catch (IOException e) {Print.print("RoomConnection-confirmUser()-Exception!!");Print.print(e.getMessage());}
				try {Thread.sleep(50);} catch (InterruptedException e) {}
				try {sendingDatagram.send(new DatagramPacket(buf, buf.length, manager.broadcastIP, privatePort));} catch (IOException e) {Print.print("RoomConnection-confirmUser()-Exception!!");Print.print(e.getMessage());}
				try {Thread.sleep(50);} catch (InterruptedException e) {}
				try {sendingDatagram.send(new DatagramPacket(buf, buf.length, manager.broadcastIP, privatePort));} catch (IOException e) {Print.print("RoomConnection-confirmUser()-Exception!!");Print.print(e.getMessage());}
				try {Thread.sleep(50);} catch (InterruptedException e) {}
				try {sendingDatagram.send(new DatagramPacket(buf, buf.length, manager.broadcastIP, privatePort));} catch (IOException e) {Print.print("RoomConnection-confirmUser()-Exception!!");Print.print(e.getMessage());}
				try {Thread.sleep(50);} catch (InterruptedException e) {}
				try {sendingDatagram.send(new DatagramPacket(buf, buf.length, manager.broadcastIP, privatePort));} catch (IOException e) {Print.print("RoomConnection-confirmUser()-Exception!!");Print.print(e.getMessage());}
				try {Thread.sleep(200);} catch (InterruptedException e) {}
				try {sendingDatagram.send(new DatagramPacket(buf, buf.length, manager.broadcastIP, privatePort));} catch (IOException e) {Print.print("RoomConnection-confirmUser()-Exception!!");Print.print(e.getMessage());}
				try {Thread.sleep(50);} catch (InterruptedException e) {}
				try {sendingDatagram.send(new DatagramPacket(buf, buf.length, manager.broadcastIP, privatePort));} catch (IOException e) {Print.print("RoomConnection-confirmUser()-Exception!!");Print.print(e.getMessage());}
				try {Thread.sleep(50);} catch (InterruptedException e) {}
				try {sendingDatagram.send(new DatagramPacket(buf, buf.length, manager.broadcastIP, privatePort));} catch (IOException e) {Print.print("RoomConnection-confirmUser()-Exception!!");Print.print(e.getMessage());}
				try {Thread.sleep(50);} catch (InterruptedException e) {}
				try {sendingDatagram.send(new DatagramPacket(buf, buf.length, manager.broadcastIP, privatePort));} catch (IOException e) {Print.print("RoomConnection-confirmUser()-Exception!!");Print.print(e.getMessage());}
				try {Thread.sleep(50);} catch (InterruptedException e) {}
				try {sendingDatagram.send(new DatagramPacket(buf, buf.length, manager.broadcastIP, privatePort));} catch (IOException e) {Print.print("RoomConnection-confirmUser()-Exception!!");Print.print(e.getMessage());}
			}
		}).start();
	}

	// ************************************** Creating a Game *************************************

	// *********************************** Connecting to a Game ***********************************

	public RoomConnection(final ConnectionDiscover manager, OnNewMessageListener listener, OnGameStartListener startingGameListener, OnConnectionConfirmedListener connectionConfirmed, String gameName, String userName, String password, int port, boolean active) throws IOException {

		// Saving instances
		this.manager = manager;
		this.listener = listener;
		this.startingGameListener = startingGameListener;
		this.confimationListener = connectionConfirmed;
		this.privatePort = port;

		// Flags
		this.running = true;
		this.announcingGame = false;
		this.gameCreator = false;
		this.connectionConfirmed = false;

		// Setting the minimum datagram size
		if(listener.messageSize > P.MIN_BYTES_BUFFER_SIZE)
			MAX_MESSAGE_SIZE = listener.messageSize;
		else
			MAX_MESSAGE_SIZE = P.MIN_BYTES_BUFFER_SIZE;

		// Opening the room
		usersInRoom = new TreeMap<String, User>();

		// Creating the packet with the announcing information
		announcingGameXml = new XmlBuilder(P.ENCABEZADO);
		announcingGameXml.addChildren(new XmlBuilder(P.PROTOCOL_VERSION).setValue(P.VERSION_CODE));
		XmlBuilder trigger = new XmlBuilder(P.CONNECTION_SYNC);
		trigger.addChildren(new XmlBuilder(P.GAME_NAME).setValue(gameName));
		trigger.addChildren(new XmlBuilder(P.USERNAME).setValue(userName));
		trigger.addChildren(new XmlBuilder(P.PASSWORD).setValue(password));
		announcingGameXml.addChildren(trigger);

		// Initializing the socket to receive
		sendingDatagram = new DatagramSocket();
		privateGameSocketToReceive = new MulticastSocket(privatePort);
		privateGameSocketToReceive.joinGroup(manager.broadcastIP);

		// Listening thread
		listeningGameThread();

		// Sending messageToGetConnected
		if(active) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					byte[] buf = announcingGameXml.toString().getBytes();
					boolean sendAtLeastOnce = false;
					try {sendingDatagram.send(new DatagramPacket(buf, buf.length, manager.broadcastIP, privatePort));sendAtLeastOnce = true;} catch (IOException e1) {}
					try {Thread.sleep(50);} catch (InterruptedException e) {}
					try {sendingDatagram.send(new DatagramPacket(buf, buf.length, manager.broadcastIP, privatePort));sendAtLeastOnce = true;} catch (IOException e1) {}
					try {Thread.sleep(50);} catch (InterruptedException e) {}
					try {sendingDatagram.send(new DatagramPacket(buf, buf.length, manager.broadcastIP, privatePort));sendAtLeastOnce = true;} catch (IOException e1) {}
					try {Thread.sleep(50);} catch (InterruptedException e) {}
					try {sendingDatagram.send(new DatagramPacket(buf, buf.length, manager.broadcastIP, privatePort));sendAtLeastOnce = true;} catch (IOException e1) {}
					try {Thread.sleep(50);} catch (InterruptedException e) {}
					try {sendingDatagram.send(new DatagramPacket(buf, buf.length, manager.broadcastIP, privatePort));sendAtLeastOnce = true;} catch (IOException e1) {}
					try {Thread.sleep(200);} catch (InterruptedException e) {}
					try {sendingDatagram.send(new DatagramPacket(buf, buf.length, manager.broadcastIP, privatePort));sendAtLeastOnce = true;} catch (IOException e1) {}
					try {Thread.sleep(50);} catch (InterruptedException e) {}
					try {sendingDatagram.send(new DatagramPacket(buf, buf.length, manager.broadcastIP, privatePort));sendAtLeastOnce = true;} catch (IOException e1) {}
					try {Thread.sleep(50);} catch (InterruptedException e) {}
					try {sendingDatagram.send(new DatagramPacket(buf, buf.length, manager.broadcastIP, privatePort));sendAtLeastOnce = true;} catch (IOException e1) {}
					try {Thread.sleep(50);} catch (InterruptedException e) {}
					try {sendingDatagram.send(new DatagramPacket(buf, buf.length, manager.broadcastIP, privatePort));sendAtLeastOnce = true;} catch (IOException e1) {}
					try {Thread.sleep(50);} catch (InterruptedException e) {}
					try {sendingDatagram.send(new DatagramPacket(buf, buf.length, manager.broadcastIP, privatePort));sendAtLeastOnce = true;} catch (IOException e1) {}
					canSend = sendAtLeastOnce;
				}
			}).start();
		}
	}

	// *********************************** Connecting to a Game ***********************************

	// ************************************* Playing the Game *************************************

	public void listeningGameThread() {

		if(!announcingGame) {

			new Thread(new Runnable() {

				@Override
				public void run() {

					while(running) {

						byte[] buf = new byte[MAX_MESSAGE_SIZE];
						DatagramPacket paquete = new DatagramPacket(buf, buf.length);
						try {
							privateGameSocketToReceive.receive(paquete);

							String message = new String(buf);

							// Checking if is a sync message
							message = XmlParser.readByTag(message, P.ENCABEZADO, 0);
							if(message != null) {
								if(XmlParser.readByTag(message, P.CONNECTION_SYNC, 0) != null) {

									String confirmation = XmlParser.readByTag(message, P.CONFIRMATION, 0);
									// If is a confirmation message
									if(confirmation != null) {
										if(!connectionConfirmed) {
											String ipHost = XmlParser.readByTag(message, P.HOST_IP, 0);
											if(manager.localIp.equals(ipHost)) {
												if(confirmation.equals(P.TRUE)) {
													confimationListener.onConnectionConfirmed(true);
												} else if(confirmation.equals(P.FALSE)){
													confimationListener.onConnectionConfirmed(false);
													running = false;
												}
												connectionConfirmed = true;
											}
										}
									} else {

										try {
											int countDown = Integer.valueOf(XmlParser.readByTag(message, P.COUNT_DOWN, 0));
											if(countDown < consecutive) {
												consecutive = countDown;
												fuzziness --; // If I receive the message it means the network is not very fuzzy
												long timeDifference = System.currentTimeMillis() - Long.valueOf(XmlParser.readByTag(message, P.LOCALTIME, 0));
												startingGameListener.onGameStart(timeDifference, countDown);
												String usersIpString = XmlParser.readByTag(message, P.USERS_LIST_IP, 0);
												String usersNamesString = XmlParser.readByTag(message, P.USERS_LIST_NAMES, 0);
												Print.print("Users Ips: "+usersIpString);
												Print.print("Users Names: "+usersNamesString);
												String[] usersIp = usersIpString.split(" ");
												String[] usersNames = usersNamesString.split(" ");
												for (int i = 0; i < usersIp.length; i++) {
													User nuevo = new User(usersIp[i], usersNames[i]);
													Print.print("Usuario agregado: "+nuevo.toString());
													if(!usersIp[i].equals(manager.localIp))
														usersInRoom.put(usersIp[i], nuevo);
												}
											}
										} catch(Exception e) {
											Print.print("Error recibiendo la lista de usuarios");
										}
									}
								}

								// Receiving a message during the game
								else {

									// Getting sender
									User sender = usersInRoom.get(paquete.getAddress().getHostAddress());

									if(sender != null) {

										String ack = XmlParser.readByTag(message, P.ACK, 0);
										if(ack != null) {
											long ackValue = Integer.valueOf(XmlParser.readByTag(message, P.ACK, 0));
//											Print.print("ACK "+ackValue);
											int fuzyDifference = (int) (ackValue - lastFuzzyIndexReceived);
											if(fuzyDifference > 0) {
//												System.out.println("Limite inferior: "+(fuzziness/3)+" Limite superior: "+(fuzziness/2)+" Diferencia: "+fuzyDifference);
												if(fuzyDifference < (fuzziness/3)) {
													if(fuzyDifference < DEFAULT_MINIMUM_VALUE && fuzziness > DEFAULT_MINIMUM_VALUE)
														fuzziness --;
													if(fuzziness > DEFAULT_MINIMUM_VALUE)
														fuzziness --;
												} else if(fuzyDifference > (fuzziness/2)) {
													fuzziness += (fuzyDifference/3);
													if(fuzziness > DEFAULT_TRUSTED_VALUE)
														fuzziness = DEFAULT_TRUSTED_VALUE;
												}
												lastFuzzyIndexReceived = ackValue;
												if(sendingTime > 100000) {
													sendingTime = System.currentTimeMillis()-sendingTime;
													Print.print("ACK received "+sendingTime+"ms after sending the message.");
												}
//												Print.print("---> Fuzziness("+gameName+"): "+fuzziness);
											}
											Print.print(fuzziness+(fuzziness < 10 ? "  " : " ")+ackValue);

										} else {

											String consecutiveText = XmlParser.readByTag(message, P.CONSECUTIVE, 0);
											String fuzzyControlText = XmlParser.readByTag(message, P.FUZZI_CONTROL, 0);
											long msjConsecutive = Long.valueOf(consecutiveText);
											//											long fuzzyIndex = Long.valueOf(fuzzyControlText);
											//											Print.print("receiving -> "+consecutiveText+"/"+fuzzyControlText);

											if(msjConsecutive > sender.lastConsecutiveIndex) {
												listener.onNewMessage(XmlParser.readByTag(message, P.PAYLOAD, XmlParser.guessNextLimit(P.CONSECUTIVE, consecutiveText, 0)) +
														XmlParser.guessNextLimit(P.FUZZI_CONTROL, fuzzyControlText, 0), sender);
												sender.lastConsecutiveIndex = msjConsecutive;
											}
											// ACK
											buf = ("<"+P.ENCABEZADO+"><"+P.ACK+">"+fuzzyControlText+"</"+P.ACK+"></"+P.ENCABEZADO+">").getBytes();
											sendingDatagram.send(new DatagramPacket(buf, buf.length, manager.broadcastIP, privatePort));
										}

									}
									//									else if(manager.localIp.equals(paquete.getAddress().getHostAddress())) {
									//									}
								}
							} else {
								Print.print("Message format incorrect!!");
							}

						} catch (Exception e) {
							Print.print("RoomConnection-listeningGameThread()-Exception!!");
							Print.print(e.getMessage());
							e.printStackTrace();
						}

					}

				}
			}).start();;
		}

	}

	//	public boolean sendMessage(final String message) {
	//
	//		if(!modeAnnouncing && canSend) {
	//
	//			new Thread(new Runnable() {
	//
	//				@Override
	//				public void run() {
	//
	//					try {
	//
	//						consecutive ++;
	//						//						byte[] buf = ("<"+P.ENCABEZADO+">"+message+"</"+P.ENCABEZADO+">").getBytes();
	//						byte[] buf = ("<"+P.ENCABEZADO+"><"+P.CONSECUTIVE+">"+(consecutive+2)+"</"+P.CONSECUTIVE+"><"+P.PAYLOAD+">"+message+"</"+P.PAYLOAD+"></"+P.ENCABEZADO+">").getBytes();
	//						sendingDatagram.send(new DatagramPacket(buf, buf.length, manager.broadcastIP, privatePort));
	//						buf = ("<"+P.ENCABEZADO+"><"+P.CONSECUTIVE+">"+(consecutive+1)+"</"+P.CONSECUTIVE+"><"+P.PAYLOAD+">"+message+"</"+P.PAYLOAD+"></"+P.ENCABEZADO+">").getBytes();
	//						sendingDatagram.send(new DatagramPacket(buf, buf.length, manager.broadcastIP, privatePort));
	//						buf = ("<"+P.ENCABEZADO+"><"+P.CONSECUTIVE+">"+consecutive+"</"+P.CONSECUTIVE+"><"+P.PAYLOAD+">"+message+"</"+P.PAYLOAD+"></"+P.ENCABEZADO+">").getBytes();
	//						sendingDatagram.send(new DatagramPacket(buf, buf.length, manager.broadcastIP, privatePort));
	//						consecutive ++;
	//
	//					} catch (Exception e) {
	//						Print.print("RoomConnection-sendMessage()-Exception!!");
	//						Print.print(e.getMessage());
	//					}
	//				}
	//			}).start();
	//		} else
	//			return false;
	//
	//		return true;
	//	}

	public boolean sendMessage(final String message, final int timesToSend) {
		if(!announcingGame) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					if(!canSend) {
						if(timesToSend > 1) {
							try {sendingDatagram.wait(10);} catch (Exception e) {}
						}
					}
					if(canSend || timesToSend > 1) {
						canSend = false;
						try {
							byte[] buf = null;
							String posterior = "</"+P.FUZZI_CONTROL+"><"+P.PAYLOAD+">"+message+"</"+P.PAYLOAD+"></"+P.ENCABEZADO+">";
							long fuzzyTarget = fuzzyIndex;
							sendingTime = System.currentTimeMillis();
							Sending_while_no_ack:
							for (int times = 1; times > 0; times--) {
								String anterior = "<"+P.ENCABEZADO+"><"+P.CONSECUTIVE+">"+(consecutive+times)+"</"+P.CONSECUTIVE+"><"+P.FUZZI_CONTROL+">";
								for (int fuz = 0; fuz < fuzziness; fuz++) {
									buf = (anterior+(fuzzyIndex++)+posterior).getBytes();
									try{sendingDatagram.send(new DatagramPacket(buf, buf.length, manager.broadcastIP, privatePort));} catch (Exception e) {Print.print("RoomConnection-sendMessage()-Exception!!");}
									try {Thread.sleep(2);} catch (Exception e) {}
									if(lastFuzzyIndexReceived > fuzzyTarget) {
										Print.print("Needend tries: "+(fuzzyIndex-fuzzyTarget));
										break Sending_while_no_ack;
									}
								}
							}
							consecutive += timesToSend;
						} catch (Exception e) {
							Print.print("RoomConnection-sendMessage()-Exception!!");
							Print.print(e.getMessage());
						}
						canSend = true;
						try {sendingDatagram.notify();} catch(Exception e) {}
					}
				}
			}).start();
		} else
			return false;
		return true;
	}

	public boolean sendMessage(final String message) {
		return sendMessage(message, 1);
	}

	// ************************************* Playing the Game *************************************

}
